//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-14     ZhangYihua   first version
//
// Description  : signed data saturation and truncation
//################################################################################

module s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
parameter           IDW                     = 9,    // input data width, (IDW-IFW)>=(ODW-OFW)
parameter           IFW                     = 5,    // input fractional width,  IFW>=IDW is legal
parameter           ODW                     = 7,    // output data width
parameter           OFW                     = 4,    // output fractional width, OFW>=ODW is legal
parameter           TRU_MODE                = "CBB_DEFINE"  // default truncation mode follows cbb_define.v 
//parameter           TRU_MODE                = "FLOOR"   // discade fractional bits directly for less area and higher Fmax
//parameter           TRU_MODE                = "ROUND"   // discade or carry according to MSB of fractonal bits for better DC
) ( 
input       signed  [IDW-1:0]               id,     // s(IDW, IFW), the MSB is sign
output      signed  [ODW-1:0]               od,     // s(ODW, OFW), the MSB is sign
output                                      over
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          TW                      = IFW-OFW;      // truncation width
localparam          SW                      = IDW-ODW-TW;   // saturation width
localparam          HW                      = SW+1;

localparam          ROUNDING                = (TRU_MODE=="FLOOR") ? 1'b0 :
                                              (TRU_MODE=="ROUND") ? 1'b1 :
`ifdef TRUNCATION_WITH_ROUNDING
                                                                    1'b1;
`else
                                                                    1'b0;
`endif

wire                                        isign;
wire        signed  [ODW-1:0]               opeak;
wire                [HW-1:0]                hd;
wire                                        hd_one;
wire                                        hd_zero;
//################################################################################
// main
//################################################################################

assign isign = id[IDW-1];
assign opeak = {ODW{isign}} ^ ({ODW{1'b1}}>>1); // reverse lower bits, keep sign bit, ODW==1 is legal

assign hd = id[IDW-1-:HW];  // saturation bits plus output sign bit
assign hd_one  = &hd;       // all bits are one
assign hd_zero = ~(|hd);    // all bits are zero

generate if (ROUNDING==1'b1 && TW>0) begin:G_RND
    wire                [ODW-1:0]               tl;
    wire                                        tl_one;
    wire                [ODW-1:0]               tmp_inc;

    assign tl = id[TW-1+:ODW];
    assign tl_one  = &tl;
    assign over = ~(hd_one | (hd_zero & (~tl_one)));
    assign tmp_inc = id[TW+:ODW]+id[TW-1];
    assign od   = (over==1'b0) ? tmp_inc : opeak;
end else begin:G_NRND
    assign over = (~hd_one) & (~hd_zero);
    assign od   = (over==1'b0) ? id[TW+:ODW] : opeak;
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (IFW<OFW) begin
        $error("input fractional width is less than output fractional width.");
        $stop;
    end

    if (IDW<ODW+TW) begin
        $error("input total width is less than output total width.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
